package pl.edu.agh.neuraleconomy.ui.component;

import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.apache.log4j.Logger;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;

import pl.edu.agh.neuraleconomy.common.configuration.Configuration;
import pl.edu.agh.neuraleconomy.common.utils.DateUtils;
import pl.edu.agh.neuraleconomy.core.exception.CoreException;
import pl.edu.agh.neuraleconomy.core.simulation.Share;
import pl.edu.agh.neuraleconomy.core.simulation.SimulationEngine;
import pl.edu.agh.neuraleconomy.core.simulation.Transaction;
import pl.edu.agh.neuraleconomy.core.ta.advice.Advice;
import pl.edu.agh.neuraleconomy.core.ta.advice.AdviceType;
import pl.edu.agh.neuraleconomy.core.ta.advice.AdvisorComposite;
import pl.edu.agh.neuraleconomy.core.ta.advice.EMAAdvisor;
import pl.edu.agh.neuraleconomy.core.ta.advice.IAdvisor;
import pl.edu.agh.neuraleconomy.core.ta.advice.PredictionAdvisor;
import pl.edu.agh.neuraleconomy.core.ta.advice.ROCAdvisor;
import pl.edu.agh.neuraleconomy.core.ta.advice.RSIAdvisor;
import pl.edu.agh.neuraleconomy.model.exchange.Company;
import pl.edu.agh.neuraleconomy.model.simulation.TransactionType;
import pl.edu.agh.neuraleconomy.persistence.base.DaoProvider;
import pl.edu.agh.neuraleconomy.persistence.exchange.CompanyDao;
import pl.edu.agh.neuraleconomy.ui.Context;
import pl.edu.agh.neuraleconomy.ui.util.UIUtils;

public class SimulationPanel extends Composite {

	private SimulationEngine simulation;
	private CompanyDao companyDao = DaoProvider.getCompanyDao();
	private Logger logger = Logger.getLogger(getClass());
	private IAdvisor advisor;

	private Group actionsGroup;
	private Group sharesGroup;
	private Group historyGroup;
	private Group statusGroup;
	private Group adviceGroup;

	private Composite historyPanel;

	private Label nameLabel;
	private Label startMoneyLabel;
	private Label startDateLabel;
	private Label currentMoneyLabel;
	private Label currentDateLabel;
	private Label wealthLabel;

	private Map<String, Label> sharesLabelsMap = new HashMap<String, Label>();
	
	private Set<Advice> advices = new TreeSet<Advice>(new Comparator<Advice>() {
		public int compare(Advice o1, Advice o2) {
			if(o2.getCertainty().equals(o1.getCertainty())){
				return -1;
			}
			return o2.getCertainty().compareTo(o1.getCertainty());
		}
	});

	public SimulationPanel(Composite parent, int style) {
		super(parent, style);
		init();
	}

	private void init() {
		GridLayout gridLayout = new GridLayout();
		gridLayout.numColumns = 3;
		this.setLayout(gridLayout);
		this.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

		initActionsGroup();
		initStatusGroup();
		initAdviceGroup();
		initSharesGroup();
		initHistoryGroup();

		refreshComponents();
		
		initAdvisor();
	}
	
	private void initAdvisor(){
		advisor = new AdvisorComposite();
		AdvisorComposite comp = (AdvisorComposite)advisor;
		
		if(Configuration.getAdvisorRoc() > 0.0){
			comp.addAdvisor(new ROCAdvisor(Configuration.getRocPeriod()), Configuration.getAdvisorRoc());
		}
		
		if(Configuration.getAdvisorEma() > 0.0){
			comp.addAdvisor(new EMAAdvisor(Configuration.getEmaPeriod()), Configuration.getAdvisorEma());
		}
		
		if(Configuration.getAdvisorPrediction() > 0.0){
			comp.addAdvisor(new PredictionAdvisor(), Configuration.getAdvisorPrediction());
		}
		
		if(Configuration.getAdvisorRsi() > 0.0){
			comp.addAdvisor(new RSIAdvisor(Configuration.getRsiPeriod()), Configuration.getAdvisorRsi());
		}
	}

	private void initAdviceGroup() {
		adviceGroup = new Group(this, SWT.SHADOW_IN);
		adviceGroup.setText("Advices");

		GridData data = new GridData();
		data.horizontalAlignment = SWT.FILL;
		data.verticalAlignment = SWT.FILL;
		data.grabExcessVerticalSpace = false;
		data.grabExcessHorizontalSpace = false;
		data.verticalSpan = 2;

		adviceGroup.setLayoutData(data);
		adviceGroup.setLayout(new GridLayout(3, false));

	}

	private void initHistoryGroup() {
		historyGroup = new Group(this, SWT.SHADOW_IN);
		historyGroup.setText("Transaction history");

		GridData data = new GridData();
		data.horizontalAlignment = SWT.FILL;
		data.verticalAlignment = SWT.FILL;
		data.grabExcessVerticalSpace = true;
		data.grabExcessHorizontalSpace = true;

		historyGroup.setLayoutData(data);

		historyGroup.setLayout(new GridLayout(4, false));

		final ScrolledComposite scrolled = new ScrolledComposite(historyGroup, SWT.V_SCROLL | SWT.H_SCROLL);
		scrolled.setExpandHorizontal(true);
		scrolled.setExpandVertical(true);
		scrolled.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
		scrolled.setLayout(new GridLayout(1, false));

		historyPanel = new Composite(scrolled, SWT.NONE);
		historyPanel.setLayout(new GridLayout(4, false));
		historyPanel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

		scrolled.setContent(historyPanel);
		scrolled.addControlListener(new ControlAdapter() {
			public void controlResized(ControlEvent e) {
				Rectangle r = scrolled.getClientArea();
				scrolled.setMinSize(historyPanel.computeSize(r.width, SWT.DEFAULT));
			}
		});
	}

	private void initSharesGroup() {
		sharesGroup = new Group(this, SWT.SHADOW_IN);
		sharesGroup.setText("Shares owned");

		GridData data = new GridData();
		data.horizontalAlignment = SWT.FILL;
		data.verticalAlignment = SWT.FILL;
		data.grabExcessVerticalSpace = true;
		sharesGroup.setLayoutData(data);

		sharesGroup.setLayout(new GridLayout(2, false));
	}

	private void initStatusGroup() {
		statusGroup = new Group(this, SWT.SHADOW_IN);
		statusGroup.setText("Status");

		GridData data = new GridData();
		data.horizontalAlignment = SWT.FILL;
		data.verticalAlignment = SWT.FILL;
		data.grabExcessHorizontalSpace = true;
		statusGroup.setLayoutData(data);

		statusGroup.setLayout(new GridLayout(2, false));

		(new Label(statusGroup, SWT.NONE)).setText("Simulation name: ");
		nameLabel = new Label(statusGroup, SWT.NONE);
		nameLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

		(new Label(statusGroup, SWT.NONE)).setText("Start date: ");
		startDateLabel = new Label(statusGroup, SWT.NONE);
		startDateLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
		(new Label(statusGroup, SWT.NONE)).setText("Start money: ");
		startMoneyLabel = new Label(statusGroup, SWT.NONE);
		startMoneyLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

		(new Label(statusGroup, SWT.NONE)).setText("Current date: ");
		currentDateLabel = new Label(statusGroup, SWT.NONE);
		currentDateLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
		(new Label(statusGroup, SWT.NONE)).setText("Current money: ");
		currentMoneyLabel = new Label(statusGroup, SWT.NONE);
		currentMoneyLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

		(new Label(statusGroup, SWT.NONE)).setText("Wealth: ");
		wealthLabel = new Label(statusGroup, SWT.NONE);
		wealthLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
	}

	private void initActionsGroup() {
		actionsGroup = new Group(this, SWT.SHADOW_IN);
		actionsGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
		actionsGroup.setText("Actions");
		actionsGroup.setLayout(new GridLayout(1, false));

		Button buySharesButton = new Button(actionsGroup, SWT.NONE);
		buySharesButton.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
		buySharesButton.setText("Buy shares");
		buySharesButton.addSelectionListener(new SelectionAdapter() {

			@Override
			public void widgetSelected(SelectionEvent arg0) {
				buyShares(null);
			}
		});

		Button sellSharesButton = new Button(actionsGroup, SWT.NONE);
		sellSharesButton.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
		sellSharesButton.setText("Sell shares");
		sellSharesButton.addSelectionListener(new SelectionAdapter() {

			@Override
			public void widgetSelected(SelectionEvent arg0) {
				sellShares(null);
			}
		});

		Button nextDayButton = new Button(actionsGroup, SWT.NONE);
		nextDayButton.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
		nextDayButton.setText("Next day");
		nextDayButton.addSelectionListener(new SelectionAdapter() {

			@Override
			public void widgetSelected(SelectionEvent arg0) {
				nextDay();
			}
		});

		Button calcAdvicesButton = new Button(actionsGroup, SWT.NONE);
		calcAdvicesButton.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
		calcAdvicesButton.setText("Calculate advices");
		calcAdvicesButton.addSelectionListener(new SelectionAdapter() {

			@Override
			public void widgetSelected(SelectionEvent arg0) {
				calculateAdvices();
			}
		});
	}

	private void buyShares(Company selectedCompany) {
		TransactionDialog dialog = showTransactionDialog(companyDao.getAll(), selectedCompany);
		if (dialog != null) {
			Transaction t = Transaction.getBuyTransaction(dialog.getCompany(), dialog.getAmmount(), dialog.getCurrentDate(),
					Context.getSimulation(), null);
			makeTransaction(t);
		}
	}

	private void sellShares(Company selectedCompany) {
		TransactionDialog dialog = showTransactionDialog(Context.getSimulation().getSharesMap().keySet(), selectedCompany);
		if (dialog != null) {
			Transaction t = Transaction.getSellTransaction(dialog.getCompany(), dialog.getAmmount(), dialog.getCurrentDate(),
					Context.getSimulation(), null);
			makeTransaction(t);

		}
	}

	private void makeTransaction(Transaction t) {
		try {
			simulation.makeTransaction(t);
			addTransaction(t);
			refreshComponents();
		} catch (CoreException e) {
			logger.warn(e.getMessage());
			UIUtils.showError(e.getMessage());
		}
	}

	private void nextDay() {
		if (simulation != null) {
			try {
				simulation.nextDay();
				clearAdvices();
				refreshComponents();
			} catch (CoreException e) {
				logger.warn(e.getMessage());
				UIUtils.showError(e.getMessage());
			}
		}
	}

	private TransactionDialog showTransactionDialog(Collection<Company> companyList, Company selectedCompany) {
		if (simulation == null) {
			return null;
		}
		TransactionDialog dialog = new TransactionDialog(Display.getDefault().getActiveShell(), companyList, simulation.getCurrentDate());
		dialog.create();
		dialog.selectCompany(selectedCompany);
		if (dialog.open() == Window.OK) {
			return dialog;
		} else {
			return null;
		}
	}

	public void refresh() {
		clearComponents();

		simulation = Context.getSimulation();
		if (simulation == null) {
			return;
		}

		refreshTransactionsPanel();
		refreshComponents();
	}

	private void clearComponents() {
		for (Control c : sharesGroup.getChildren()) {
			c.dispose();
		}
		sharesLabelsMap.clear();

		for (Control c : historyPanel.getChildren()) {
			c.dispose();
		}

		clearAdvices();
	}

	private void refreshComponents() {
		refreshStatusGroup();
		refreshSharesGroup();
		calculateAdvicesForOwnedShares();

		this.layout(true, true);
	}

	private void refreshStatusGroup() {
		nameLabel.setText(simulation == null ? "-" : simulation.getName());

		startDateLabel.setText(simulation == null ? "-" : DateUtils.formatDate(simulation.getStartDate()));
		currentDateLabel.setText(simulation == null ? "-" : DateUtils.formatDate(simulation.getCurrentDate()));

		startMoneyLabel.setText(simulation == null ? "-" : String.valueOf(simulation.getStartMoney()));
		currentMoneyLabel.setText(simulation == null ? "-" : String.valueOf(simulation.getCurrentMoney()));

		wealthLabel.setText(simulation == null ? "-" : String.valueOf(simulation.getWealth()));
	}

	private void refreshSharesGroup() {
		if (simulation != null) {
			Map<Company, Share> shares = simulation.getSharesMap();
			for (Company c : shares.keySet()) {
				setShareAmmount(c, shares.get(c).getAmount());
			}
		}
	}

	private void setShareAmmount(Company company, int ammount) {
		Label shareLabel = sharesLabelsMap.get(company.getName());
		if (shareLabel == null) {
			getCompanyLabel(sharesGroup, SWT.NONE, company).setText(company.getName());
			shareLabel = new Label(sharesGroup, SWT.NONE);
			sharesLabelsMap.put(company.getName(), shareLabel);
		}
		shareLabel.setText(String.valueOf(ammount));
	}

	private void refreshTransactionsPanel() {
		if (Context.getSimulation() == null) {
			return;
		}

		for (Transaction t : Context.getSimulation().getTransactions()) {
			addTransaction(t);
		}
	}

	private void addTransaction(Transaction t) {
		Label plusminus = new Label(historyPanel, SWT.BOLD);
		plusminus.setText(TransactionType.BUY.equals(t.getType()) ? "+" : "-");
		final Color color = TransactionType.BUY.equals(t.getType()) ? new Color(getDisplay(), 0, 255, 0) : new Color(getDisplay(), 255, 0,
				0);
		plusminus.setForeground(color);
		plusminus.addDisposeListener(new DisposeListener() {
			public void widgetDisposed(DisposeEvent e) {
				color.dispose();
			}
		});

		getCompanyLabel(historyPanel, SWT.NONE, t.getCompany()).setText(t.getCompany().getName());
		(new Label(historyPanel, SWT.NONE)).setText(String.valueOf(t.getAmount()));
		(new Label(historyPanel, SWT.NONE)).setText(DateUtils.formatDate(t.getDate()));

	}

	private void clearAdvices() {
		for (Control c : adviceGroup.getChildren()) {
			c.dispose();
		}
	}

	private void calculateAdvicesForOwnedShares() {
		clearAdvices();
		if (simulation != null) {
			for (Company c : simulation.getSharesMap().keySet()) {
				Advice advice = advisor.getAdvice(c, simulation.getCurrentDate());
				if (advice != null) {
					addAdvice(advice, false);
				}
			}
		}
	}

	private void calculateAdvices() {
		clearAdvices();

		if (simulation == null) {
			return;
		}

		try {
			new ProgressMonitorDialog(getShell()).run(true, false, new IRunnableWithProgress() {

				public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
					Collection<Company> companies = companyDao.getAll().subList(0, 100);
					monitor.beginTask("Calculating advices", companies.size());
					advices.clear();

					for (Company c : companies) {
						monitor.subTask(c.getName());
						

						Advice advice = advisor.getAdvice(c, simulation.getCurrentDate());
						if (advice != null) {
							if ((AdviceType.BUY.equals(advice.getType()))
									|| (AdviceType.SELL.equals(advice.getType()) && simulation.getSharesAmmount(advice.getCompany()) > 0))
								advices.add(advice);
						}

						monitor.worked(1);
					}
					
					monitor.done();
				}
			});
		} catch (Exception e) {
			logger.error("Error while calculating advices", e);
			UIUtils.showError("Error while calculating advices. See a log for more informations");
		}

		for (Advice a : advices) {
			addAdvice(a, false);
		}

		this.layout(true, true);
	}

	private void addAdvice(Advice advice, boolean relayout) {
		if (advice == null) {
			return;
		}
		LinkLabel<Advice> companyLabel = new LinkLabel<Advice>(adviceGroup, SWT.BOLD, advice) {
			@Override
			protected void onClick(Advice object) {
				Context.getChartPanel().adjustPredictionPanel(true, DateUtils.addDays(simulation.getCurrentDate(), 1), 5);
				Context.getChartPanel().paint(DateUtils.addDays(simulation.getCurrentDate(), -45), simulation.getCurrentDate(),
						object.getCompany());
				Context.getTabFolder().setSelection(0);
			}
		};
		companyLabel.setText(advice.getCompany().getName());

		LinkLabel<Advice> adviceLabel = new LinkLabel<Advice>(adviceGroup, SWT.NONE, advice) {
			@Override
			protected void onClick(Advice object) {
				if (AdviceType.BUY.equals(object.getType())) {
					buyShares(object.getCompany());
				} else if (AdviceType.SELL.equals(object.getType())) {
					sellShares(object.getCompany());
				}
			}
		};
		adviceLabel.setText(advice.getType().getName());

		(new Label(adviceGroup, SWT.NONE)).setText(String.valueOf(advice.getCertainty()));

		if (relayout) {
			this.layout(true, true);
		}
	}

	private Label getCompanyLabel(Composite parent, int style, Company company) {
		return new LinkLabel<Company>(parent, style, company) {

			@Override
			protected void onClick(Company object) {
				addAdvice(advisor.getAdvice(object, simulation.getCurrentDate()), true);

			}
		};
	}

}
